package aconcagua.measure

abstract class UnitBehavior {

	// (class) accessing
	protected static defaultSign() {
		return "NO SIGN";
	}

	protected static nameForManyEndString() {
		return "s";
	}

	protected static nameForManyFrom(aString) {
		return aString + nameForManyEndString();
	}

	// accessing
	abstract baseUnit();

	abstract denominator();

	public denominatorOf(aNumber) {
		return this.denominator().with(aNumber.denominator());
	}

	public name() {
		return this.nameForOne();
	}

	public nameFor(aNumber) {
		return aNumber.unitNameOf(this);
	}

	abstract nameForMany();

	abstract nameForOne();

	public nameForUndefinedAmount() {
		return this.nameForMany();
	}

	abstract nothingAmount();

	public nullMeasure() {
		return new Measure(this.baseUnit().nothingAmount(),
		this.baseUnit());
	}

	abstract numerator();

	public numeratorOf(aNumber) {
		return this.numerator().with(aNumber.numerator());
	}

	public reciprocal() {
		return this.denominator() / this.numerator();
	}

	abstract sign();

	// arithmetic operations
	public div(aNumberOrUnit) {
		return aNumberOrUnit.divideByUnit(this);
	}

	public multiply(aNumberOrUnit) {
		return aNumberOrUnit.multiplyByUnit(this);
	}
	
	public multiply(Infinity anInfinity) {
		return this.multiplyInfinity(anInfinity);
	}
	
	public multiply(MinusInfinity anInfinity) {
		return this.multiplyInfinity(anInfinity);
	}

	abstract divideByUnit(aNumberOrUnit);

	abstract multiplyByUnit(aNumberOrUnit);
	
	// arithmetic operations - private
	abstract protected divide(DividedUnit aUnit);
	
	abstract protected divide(MultipliedUnit aUnit);
	
	abstract protected divide(NullUnit aUnit);
	
	abstract protected divide(SimpleUnit aUnit);
	
	abstract protected multiply(DividedUnit aUnit);
	
	abstract protected multiply(MultipliedUnit aUnit);
	
	abstract protected multiply(NullUnit aUnit);
	
	abstract protected multiply(SimpleUnit aUnit);
	
	private multiplyInfinity(anInfinity) {
		return (anInfinity.unit() * this).with(anInfinity.amount());
	}
	
	public divideMeasure(aMeasure) {
		return ((new NullUnit() / this) * aMeasure.unit()).with(aMeasure.amount());
	}

	// converting
	abstract convertAmountToBaseUnit(aNumber);
	
	// converting - private
	abstract protected convertFromBaseUnit(aMeasure);
	
	abstract protected convertToBaseUnit(aMeasure);

	// measure creation
	abstract with(aNumber);
	
	// printing
	@Override
	public String toString() {
		return this.name().toString();
	}

	// testing
	public isZero() {
		return this.isNothing();
	}

	public sameDomainAs(aUnit) {
		return this.baseUnit() == aUnit.baseUnit();
	}
}
